package com.magima.core.basic;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import android.os.Handler;
import android.os.Looper;

import com.magima.core.basic.option.StorageOptionConstants;
import com.magima.core.connect.listener.ServerDataListener;

public class Collection<T> {
	public interface Listener<T> {
		void onAddModel(Model<T> model, Collection<T> collection);

		void onRemoveModel(Model<T> model, Collection<T> collection);

		void onResetModel(Collection<T> collection);

		void onModelChange(Model<T> model, Collection<T> collection);
	}

	public interface UrlGenerator<T> {
		public String getUrl(Collection<T> collection);
	}

	private ICIdGetter<T> mCIdGetter;
	private Class<T> modelType;
	private List<Model<T>> list;
	private List<WeakReference<Listener<T>>> listeners = new ArrayList<WeakReference<Listener<T>>>();
	private Comparator<Model<T>> comparator;
	private Storage<T> storage = null;
	private UrlGenerator<T> urlGenerator = null;
	private boolean mFetched;
	private boolean mFetching;
	private HashMap<String, Model<T>> mIdMap;
	
	private static Handler mHandler = new Handler();//handler in main looper
	
	private Lock mListenerLock = new ReentrantLock(true);
	private Lock mListLock = new ReentrantLock(true);
	
	public void addListener(Listener<T> listener) {
		mListenerLock.lock();
		try {
			listeners.add(new WeakReference<Listener<T>>(listener));
		} finally {
			mListenerLock.unlock();
		}
	}

	public void removeListener(Listener<T> listener) {
		mListenerLock.lock();
		try {
			List<WeakReference<Listener<T>>> delList = new ArrayList<WeakReference<Listener<T>>>();
			for (Iterator<WeakReference<Listener<T>>> iterator = listeners
					.iterator(); iterator.hasNext();) {
				WeakReference<Listener<T>> weakRef = iterator.next();
				if (weakRef.get() == listener || weakRef.get() == null) {
					delList.add(weakRef);
				}
			}
			listeners.removeAll(delList);
		} finally {
			mListenerLock.unlock();
		}
	}

	public void removeAllListener(){
		mListenerLock.lock();
		try {
			listeners.clear();
		} finally {
			mListenerLock.unlock();
		}
	}
	
	public void setUrlGenerator(UrlGenerator<T> generator) {
		this.urlGenerator = generator;
	}

	public UrlGenerator<T> getUrlGenerator() {
		return this.urlGenerator;
	}

	public Collection(Class<T> type, ICIdGetter<T> cIdGetter) {
		this(new LinkedList<Model<T>>(), type, cIdGetter, null);
	}

	public Collection(List<Model<T>> list, Class<T> type, ICIdGetter<T> cIdGetter) {
		this(list, type, cIdGetter, null);
	}
	
	public Collection(Class<T> type, ICIdGetter<T> cIdGetter,
			Comparator<Model<T>> comparator) {
		this(new LinkedList<Model<T>>(), type, cIdGetter, comparator);
	}

	public Collection(List<Model<T>> list, Class<T> type, ICIdGetter<T> cIdGetter,
			Comparator<Model<T>> comparator) {
		init(list, type, cIdGetter, comparator);
	}

	private void init(List<Model<T>> list, Class<T> type, ICIdGetter<T> cIdGetter,
			Comparator<Model<T>> comparator) {
		this.list = list;
		modelType = type;
		this.mCIdGetter = cIdGetter;
		this.comparator = comparator;
		mFetched = false;
		mFetching = false;
		this.mIdMap = new HashMap<String, Model<T>>();
	}

	public void setComparator(Comparator<Model<T>> comparator) {
		this.comparator = comparator;
	}

	public Comparator<Model<T>> getComparator() {
		return this.comparator;
	}

	public void setStorage(Storage<T> storage) {
		this.storage = storage;
	}

	public Storage<T> getStorage() {
		return this.storage;
	}

	public final Model<T> add(int index, T model) {
		return add(index, model, false);
	}

	public final Model<T> add(T model) {
		return add(model, false);
	}

	public final Model<T> add(int index, T model, boolean silent) {
		if ( model == null )
			throw new NullPointerException();
		return add(index, new Model<T>(model, modelType), silent);
	}

	public final Model<T> add(T model, boolean silent) {
		return add(new Model<T>(model, modelType), silent);
	}

	public final Model<T> add(int index, Model<T> model) {
		return add(index, model, false);
	}

	public final Model<T> add(Model<T> model) {
		return add(model, false);
	}

	public final void create(T model, final StorageDataListener<T> listener) {
		create(model, listener, false, null);
	}

	public void create(T model, final StorageDataListener<T> listener,
			final boolean silent, HashMap<String, Object> options) {
		if (storage != null) {
			storage.create(this, model, new ServerDataListener<T>() {
				@Override
				public void onSuccess(T data, String resp) {
					Model<T> m = add(data, silent);
					if (listener != null)
						listener.onSuccess(m);
				}

				@Override
				public void onError() {
					if (listener != null)
						listener.onError(null);
				}
			}, options);
		} else
			add(model, silent);
	}

	public Model<T> add(Model<T> model, boolean silent) {
		return add(list.size(), model, silent);
	}

	private void notifyAddModel(Model<T> model){
		mListenerLock.lock();
		try {			
			List<WeakReference<Listener<T>>> delList = new ArrayList<WeakReference<Listener<T>>>();
			for (Iterator<WeakReference<Listener<T>>> iterator = listeners
					.iterator(); iterator.hasNext();) {
				WeakReference<Listener<T>> weakRef = iterator.next();
				if (weakRef.get() == null) {
					delList.add(weakRef);
				} else {
					weakRef.get().onAddModel(model, this);
				}
			}
			listeners.removeAll(delList);
		} finally {
			mListenerLock.unlock();
		}
	}
	
	private class AddModelRunnable implements Runnable {
		Model<T> model;
		
		public AddModelRunnable(Model<T> model){
			this.model = model;
		}
		@Override
		public void run() {
			notifyAddModel(model);
		}		
	}
	
	public Model<T> add(int index, final Model<T> model, boolean silent) {
		String id = getId(model.getModel());
		Model<T> oldModel = mIdMap.get(id);
		mListLock.lock();
		try {
			if ( oldModel != null ) {
				list.remove(oldModel);
				if ( index > list.size() )
					index = index - 1;
			}
			list.add(index, model);
			mIdMap.put(id, model);
			model.setCollection(this);
			if (comparator != null && !silent ) {
				Collections.sort(list, comparator);
			}
		} finally {
			mListLock.unlock();
		}
		
		if (!silent) {
			if ( Looper.myLooper() == Looper.getMainLooper() ) {
				notifyAddModel(model);
			} else {
				mHandler.post(new AddModelRunnable(model));
			}
		}
		if ( oldModel != null)
			return oldModel;
		return model;
	}

	public boolean isFetched() {
		return mFetched;
	}
	
	public void setFetched(boolean isFetched) {
		mFetched = isFetched;
	}
	
	public boolean isFetching(){
		return mFetching;
	}
	
	public void setFetching(boolean isFetching) {
		mFetching = isFetching;
	}
	
	public void makeSureFetched(final StorageDataListener<T> listener,
			final boolean silent,
			HashMap<String, Object> options){
		//if ( isFetching() ) //no reEnter when fetching
		//	return;
		if ( isFetched() ){
			if ( listener != null ) {
				listener.onSuccess(null);
			}
			reset(silent);
		} else {
			fetch(listener, silent, options);
		}
	}
	
	public void fetch(final StorageDataListener<T> listener) {
		fetch(listener, null);
	}
	
	public void fetch(final StorageDataListener<T> listener, HashMap<String, Object> options) {
		fetch(listener, false, options);
	}
	
	public void fetch(final StorageDataListener<T> listener,
			final boolean silent,
			HashMap<String, Object> options) {
		if (storage != null) {
			boolean pageFetchAfter = false, pageFetchBefore = false;
			if (options != null) {
				if (options.get(StorageOptionConstants.OPTION_PAGE_FETCH_AFTER) != null) {
					pageFetchAfter = (Boolean) options
							.get(StorageOptionConstants.OPTION_PAGE_FETCH_AFTER);
				}
				if (options.get(StorageOptionConstants.OPTION_PAGE_FETCH_BEFORE) != null) {
					pageFetchBefore = (Boolean) options
							.get(StorageOptionConstants.OPTION_PAGE_FETCH_BEFORE);
				}
			}
			final boolean _pageFetchAfter = pageFetchAfter, _pageFetchBefore = pageFetchBefore;
			mFetching = true;
			storage.read(this, new ServerDataListener<List<T>>() {
				@Override
				public void onSuccess(List<T> data, String resp) {
					mFetched = true;
					mFetching = false;
					if (_pageFetchAfter) {
						mListLock.lock();
						try {
							for (T t : data) {
								Model<T> model = new Model<T>(t, modelType);
								String id = getId(t);
								Model<T> oldModel = mIdMap.get(id);
								if ( oldModel != null ) {
									list.remove(oldModel);
								}
								mIdMap.put(id, model);
								list.add(list.size(), model);
								model.setCollection(Collection.this);
							}
							reset(silent);
						} finally {
							mListLock.unlock();
						}
					} else if (_pageFetchBefore) {
						mListLock.lock();
						try {
							for (int i = data.size() - 1; i >= 0; i--) {
								T t = data.get(i);
								Model<T> model = new Model<T>(t, modelType);
								String id = getId(t);
								Model<T> oldModel = mIdMap.get(id);
								if ( oldModel != null ) {
									list.remove(oldModel);
								}
								mIdMap.put(id, model);
								list.add(0, model);
								model.setCollection(Collection.this);
							}
							reset(silent);
						} finally {
							mListLock.unlock();
						}
					} else {
						reset(data, silent);
					}
					if (listener != null)
						listener.onSuccess(null);
				}

				@Override
				public void onError() {
					mFetching = false;
					if (listener != null)
						listener.onError(null);
				}
			}, options);
		}
	}

	public void reset(List<T> l) {
		reset(l, false);
	}

	public void reset(List<T> l, boolean silent) {
		mListLock.lock();
		try {
			
			if(l != null){
				List<Model<T>> tempList = new ArrayList<Model<T>>();
				HashMap<String, Model<T>> tempMap = new HashMap<String, Model<T>>();
				for ( T t : l ){
					String id = getId(t);
					Model<T> model = mIdMap.get(id);  
					if ( model != null ) { //如果已经存在原列表中，不改变Model地址
						tempList.add(model);
						model.update(t, true, false);
						tempMap.put(id, model);
					} else { //否则，新建Model
						Model<T> m = new Model<T>(t, modelType);
						tempList.add(m);
						tempMap.put(id, m);
						m.setCollection(this);
					}
				}
				mIdMap.clear();
				list.clear();
				
				mIdMap = tempMap;
				for ( Model<T> m : tempList ) {
					list.add(m);
				}
			} else {
				mIdMap.clear();
				list.clear();
			}
			/*list.clear();
			mIdMap.clear();
			if(l != null){
				for (T t : l) {
					String id = getId(t);
					Model<T> model = new Model<T>(t, modelType);
					list.add(model);
					mIdMap.put(id, model);
					model.setCollection(this);
	
				}
			}*/
			reset(silent);
		} finally {
			mListLock.unlock();
		}
	}
	
	public void notifyReset(){
		mListenerLock.lock();
		try {			
			List<WeakReference<Listener<T>>> delList = new ArrayList<WeakReference<Listener<T>>>();
			for (Iterator<WeakReference<Listener<T>>> iterator = listeners
					.iterator(); iterator.hasNext();) {
				WeakReference<Listener<T>> weakRef = iterator.next();
				if (weakRef.get() == null) {
					delList.add(weakRef);
				} else {
					weakRef.get().onResetModel(this);
				}
			}
			listeners.removeAll(delList);
		} finally {
			mListenerLock.lock();
		}
	}
	
	public void reset(boolean silent) {
		if (comparator != null) {
			mListLock.lock();
			try {
				Collections.sort(list, comparator);
			} finally {
				mListLock.unlock();
			}
		}
		if (!silent) {
			if ( Looper.myLooper() == Looper.getMainLooper() ) {
				notifyReset();
			} else {
				mHandler.post(new Runnable(){
					@Override
					public void run() {
						notifyReset();
					}				
				});
			}
		}
	}
	
	public Model<T> remove(Model<T> model) {
		return remove(model, false);
	}

	public Model<T> remove(T model) {
		return remove(model, false);
	}

	public String getId(T model) {
		return mCIdGetter.getCId(model);
	}

	public Model<T> remove(T model, boolean silent) {
		return remove(getId(model), silent);
	}

	private void notifyRemoveModel(Model<T> model){
		mListenerLock.lock();
		try {
			List<WeakReference<Listener<T>>> delList = new ArrayList<WeakReference<Listener<T>>>();
			for (Iterator<WeakReference<Listener<T>>> iterator = listeners
					.iterator(); iterator.hasNext();) {
				WeakReference<Listener<T>> weakRef = iterator.next();
				if (weakRef.get() == null) {
					delList.add(weakRef);
				} else {
					weakRef.get().onRemoveModel(model, this);
				}
			}
			listeners.removeAll(delList);
		} finally {
			mListenerLock.unlock();
		}
	}
	
	private class RemoveRunnable implements Runnable {
		boolean silent;
		Model<T> model;
		public RemoveRunnable(Model<T> model, boolean silent){
			this.model = model;
			this.silent = silent;
		}
		@Override
		public void run() {
			T tempData = model.getModel();
			String id = getId(tempData);
			mListLock.lock();
			try {
				list.remove(model);
				model.setCollection(null);
				mIdMap.remove(id);
			} finally {
				mListLock.unlock();
			}
			if (!silent) {
				notifyRemoveModel(model);
			}
		}
	
	}
	public Model<T> remove(final Model<T> model, final boolean silent) {
//		Log.e("MAGIMA","model "+Model.getId(model.getModel(), modelType, idAttribute)+"remove");
//		Exception e = new Exception();
//		e.printStackTrace();
		if ( Looper.myLooper() == Looper.getMainLooper() ) {
			mListLock.lock();
			try {
				list.remove(model);
				mIdMap.remove(getId(model.getModel()));
				model.setCollection(null);
			} finally {
				mListLock.unlock();
			}
			if (!silent) {
				notifyRemoveModel(model);
			}
		} else {
			mHandler.post(new RemoveRunnable(model, silent));
		}
		return model;
	}

	public final Model<T> get(int id) {
		return get(String.valueOf(id));
	}
	
	public final Model<T> get(long id) {
		return get(String.valueOf(id));
	}

	public Model<T> get(String id) {
		return mIdMap.get(id);
	}

	public boolean has(Model<T> model) {
		return has(model.getModel());
	}

	public boolean has(T model) {
		return has(getId(model));
	}

	public boolean has(String id) {
		return mIdMap.get(id) != null;
	}

	public final Model<T> remove(String id) {
		return remove(id, false);
	}

	public Model<T> remove(String id, boolean silent) {
		mListLock.lock();
		try {
			Model<T> model = mIdMap.get(id);
			if (model != null) {
				this.remove(model, silent);
				return model;
			}
		} finally {
			mListLock.unlock();
		}
		return null;
	}
	
	public void removeAll(final StorageDataListener<T> listener,
			final boolean silent,
			HashMap<String, Object> options){
		if (storage != null) {
			storage.deleteAll(this, new ServerDataListener<T>() {
				@Override
				public void onSuccess(T data, String resp) {
					empty();
					reset(silent);
					if ( listener != null )
						listener.onSuccess(null);
				}

				@Override
				public void onError() {
					if ( listener != null )
						listener.onError(null);
				}
			}, options);
		} else {
			empty();
			reset(silent);
		}
	}

	public List<Model<T>> getList() {
		return list;
	}

	public int length() {
		return list.size();
	}

	public void notifyModelChange(Model<T> model) {
		synchronized (listeners) {
			List<WeakReference<Listener<T>>> delList = new ArrayList<WeakReference<Listener<T>>>();
			lock();
			for (Iterator<WeakReference<Listener<T>>> iterator = listeners.iterator(); iterator.hasNext();) {
				WeakReference<Listener<T>> weakRef = iterator.next();
				if (weakRef.get() == null) {
					delList.add(weakRef);
				} else {
					weakRef.get().onModelChange(model, this);
				}
			}
			unlock();
			listeners.removeAll(delList);
		}
	}

	public interface Filter<T> {
		boolean filter(Model<T> model);
	}
	
	public void filter(Filter<T> filter, List<Model<T>> reuseList){
		reuseList.clear();
		lock();
		for (Model<T> model : list) {
			if (filter.filter(model))
				reuseList.add(model);
		}
		unlock();
	}

	public List<Model<T>> filter(Filter<T> filter) {
		ArrayList<Model<T>> result = new ArrayList<Model<T>>();
		lock();
		for (Model<T> model : list) {
			if (filter.filter(model))
				result.add(model);
		}
		unlock();
		return result;
	}
	
	public void empty(){
		list.clear();
		mIdMap.clear();
		mFetched = false;
		mFetching = false;
	}
	
	public interface EachIterator<T> {
		void each(Model<T> model, int index);
	}
	public void each(EachIterator<T> iterator){
		lock();
		try {
			for(int i = 0; i < length(); i++){
				iterator.each(list.get(i), i);
			}
		} finally {
			unlock();
		}
	}
	
	public interface MapIterator<T> {
		Class<?> map(Model<T> model);
	}
	public List<Object> map(MapIterator<T> mapper){
		lock();
		ArrayList<Object> result = new ArrayList<Object>();
		try {			
			for (Model<T> model : list) {
				result.add(mapper.map(model));
			}
		} finally {
			unlock();
		}
		return result;
	}
	
	public interface EveryIterator<T> {
		boolean every(Model<T> model);
	}
	public boolean every(EveryIterator<T> every){
		lock();
		try {			
			for (Model<T> model : list) {
				if ( !every.every(model) )
					return false;
			}
		} finally {
			unlock();
		}
		return true;
	}
	
	public interface SomeIterator<T> {
		boolean some(Model<T> model);
	}
	public boolean some(SomeIterator<T> some){
		lock();
		try {			
			for (Model<T> model : list) {
				if ( some.some(model) )
					return true;
			}
		} finally {
			unlock();
		}
		return false;
	}

	public void lock(){
		mListLock.lock();
	}
	
	public void unlock(){
		mListLock.unlock();
	}
}
